#!/usr/bin/python
###
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# VideoArea and VideoWidget from Listen
# Listen is the legal property of mehdi abaakouk <theli48@gmail.com>
# Copyright (c) 2006 Mehdi Abaakouk
###

try:
    import pygtk
    pygtk.require("2.0")
except:
    pass
try:
    import gtk
    import gtk.glade
    import gobject
except:
    sys.exit(1)

import os
#from mplayer import *
import pygst
pygst.require("0.10")
import gst


COLUMN_NUMBER = 0
COLUMN_STRING = 1


class Gui:

    def __init__(self):
        self.statuses = {'buffer':'','pos':'00:00','dur':'00:00'}
        self._query_id = -1
        self.cur_index = -1
        self.paused = False
        self.count = 0
        self.seeking = False # User is seeking, stop updating seeker from timer
        self.update_seeker = False # Update seeker to current position, from timer
        self.can_seek = False
        self.metadata = {} #Tags
                
        #Set the Glade file
        self.gladefile = "gui/gui.glade"  
        self.wTree = gtk.glade.XML(self.gladefile)
        #Get the Main Window, and connect the "destroy" event
        self.window = self.wTree.get_widget("window")
        if (self.window):
            self.window.connect("destroy", self.destroy)
        
        self.treeview = self.wTree.get_widget("treeview1")
        self.vboxplayer = self.wTree.get_widget("vboxplayer")
        self.status = self.wTree.get_widget("status")
        self.seeker = self.wTree.get_widget("seeker")
        
        self.player = gst.element_factory_make("playbin", "player")
        self.vwidget = VideoWidget(self)
        self.vwidget.show_all()
        self.vboxplayer.pack_start(self.vwidget)
        #from controls import CairoControl
        #self.clock = CairoControl()
        
        self.gconfsink = gst.element_factory_make("gconfvideosink", "my-videosink")
        self.player.set_property("video-sink", self.gconfsink)

        self.pulsesink = gst.element_factory_make("pulsesink", "my-audiosink")
        self.pulsesink.set_property("server", "192.168.2.101")
        self.player.set_property("audio-sink", self.pulsesink)

        
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("message::tag", self.on_tag_message)
        bus.connect("sync-message::element", self.on_sync_message)
        
        self.time_format = gst.Format(gst.FORMAT_TIME)
        self.time_format_percent = gst.Format(gst.FORMAT_PERCENT)
        self.time_format_bytes = gst.Format(gst.FORMAT_BYTES)
        
        r = gst.registry_get_default()
        l = [x for x in r.get_feature_list(gst.ElementFactory) \
             if (gst.ElementFactory.get_klass(x) == "Visualization")]
        
        vis = 0
        if (l != []):
            import random
            vis = random.randrange(0, len(l) ) # maybe len(l)-1
            print "Visualizer:",l[vis].get_name()
            visualizer = gst.element_factory_make(l[vis].get_name())
            self.player.set_property("vis-plugin", visualizer) # If no video, show vis
        
        dic = { "on_open_activate" : self.on_open,
            "on_quit_activate" : gtk.main_quit,
            "on_treeview1_row_activated" : self.row_activated,
            "on_buttonplay_clicked" : self.on_start_pause,
            "on_button3_clicked" : self.on_stop,
            "on_buttonprev_clicked": self.on_prev,
            "on_buttonnext_clicked": self.on_next,
            "on_vol_value_changed": self.on_vol_changed,
            "on_seeker_value_changed": self.on_seeker_value_changed,
            "on_seeker_move_slider": self.on_seeker_move_slider,
            "on_seeker_press_event": self.on_seeker_press_event,
            "on_seeker_release_event": self.on_seeker_release_event }
            
        self.wTree.signal_autoconnect(dic)
        self.add_columns()
        self.set_status()
        gtk.gdk.notify_startup_complete()


    def set_btn_label(self,btn, text=None, stock=None):
        #label = self.wTree.get_widget(btn+"label")
        #label.set_label(text)
        if stock:
            self.wTree.get_widget(btn+"image").set_from_stock( stock,gtk.ICON_SIZE_BUTTON)
            
    def set_play_play(self):
        #self.set_btn_label("play","Play",gtk.STOCK_MEDIA_PLAY)
        pass
        
    def set_play_pause(self):
        #self.set_btn_label("play","Pause",gtk.STOCK_MEDIA_PAUSE)
        pass
        
    #def get_btn_label(self, btn="play"):
    #    return self.wTree.get_widget(btn+"label").get_label()
        
    def set_status(self):
        self.status.set_label( "%s  %s/%s"%( self.statuses['buffer'],
            self.statuses['pos'],self.statuses['dur'] ) )
    
    def on_seeker_press_event(self,w, e = None):
        #print "Drag",w,e
        self.seeking = True
        
    def on_seeker_release_event(self,w, e = None):
        #print "Drag",w,e
        self.seeking = False
        
    def on_seeker_move_slider(self,w,v):
        print "Move slider",w,v
        #<enum GTK_SCROLL_STEP_UP , PAGE_UP of type GtkScrollType>

                
    def on_seeker_value_changed(self,w): #Watch out for recursion
        if not self.update_seeker and self.can_seek:
            self.set_seek(w.get_value())
    
    def set_seek(self, pos):
        seek_ns = pos * 1000000000
        if not self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH, seek_ns):
            print "Cannot seek" #Seems like bug to me, with http gstreamer should check if server supports ranges :(
            
    def get_duration_ns(self):
        return self.player.query_duration(self.time_format, None)[0]
        
    def get_duration(self):
        return self.get_duration_ns() / 1000000000
        
    def get_duration_bytes(self):
        return self.player.query_duration(self.time_format_bytes, None)[0]
    
    def get_position_ns(self):
        return self.player.query_position(self.time_format, None)[0]

    def get_position_bytes(self):
        return self.player.query_position(self.time_format_bytes, None)[0]
        
    def on_vol_changed(self,w,v):
        #self.set_vol(w.get_value())
        self.set_vol(v)
    
    def set_vol(self,vol = None):
        if not vol:
            vol = self.wTree.get_widget("volume").get_value()
        self.player.set_property("volume",vol)
    
    def on_tag_message(self, bus, message):
        tags = message.parse_tag()
        for x in tags.keys():
            print x,"=",tags[x]
            if x != "private-id3v2-frame":
                self.metadata[x] = tags[x]
    
    def on_message(self, bus, message):
        t = message.type
        #print "Message of",t
        if t == gst.MESSAGE_EOS:
            self.player.set_state(gst.STATE_NULL)
            #self.set_play_play()
            self.on_next(None)
            
        elif t == gst.MESSAGE_ERROR:
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            print message
            self.set_play_play()
        #elif t == gst.MESSAGE_TAG:
        #    tags = message.parse_tag()
        #    for x in tags.keys():
        #        print x,"=",tags[x]
            
        elif t == gst.MESSAGE_BUFFERING:
            self.statuses['buffer'] = "%d%%"%message.parse_buffering()
            self.set_status()
            
        elif t == gst.MESSAGE_STATE_CHANGED:
            old,new,pending = message.parse_state_changed()
            
            if new == gst.STATE_PLAYING:
                pos_int = self.get_duration_ns()
                if pos_int > -1:
                    self.statuses['dur'] = self.convert_ns(pos_int)
                    self.seeker.set_range(0, pos_int / 1000000000)
                    self.can_seek = True
                else:
                    self.statuses['dur'] = '00:00'
                    self.seeker.set_range(0, 1)
                    self.can_seek = False
                self.set_status()
                self.set_vol()
                self.set_tree_file_length(self.statuses['dur'])
            elif old == gst.STATE_NULL:
                self.statuses = {'buffer':'','pos':'00:00','dur':'00:00'}
                self.set_status()

    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        message_name = message.structure.get_name()
        if message_name == "prepare-xwindow-id":
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            self.vwidget.set_sink(imagesink)

    def on_start_pause(self,w):
        
        if self.player.get_state()[1] != gst.STATE_NULL:
            if self.player.get_state()[1] == gst.STATE_PLAYING:
                self.set_play_play()
                self.player.set_state(gst.STATE_PAUSED)
                self.paused = True
            else:
                self.player.set_state(gst.STATE_PLAYING)
                self.set_play_pause()
                self.paused = False
        else:
            self.start_stop(None)
            
    def on_stop(self,w):
        try:
            if self.player:
                self.player.set_state(gst.STATE_NULL)
                self.set_play_play()
                self._stop_queries()
        except:
            print "Error on Stop"
            
    def on_next(self,w):
        self.cur_index += 1
        if self.cur_index >= self.count:
            self.cur_index = 0
        fpath = self.get_file_path((self.cur_index,))
        self.start_stop(fpath)
        
        
    def on_prev(self,w):
        self.cur_index -= 1
        if self.cur_index < 0:
            self.cur_index = self.count - 1
        fpath = self.get_file_path((self.cur_index,))
        self.start_stop(fpath)
                
    def start_stop(self, path):
        try:
            if not path and self.player:
                path = self.player.get_property('uri')
        except:
            pass
        
        if not path:
            path = self.get_file_selected()
            if not path:
                path = self.get_file_path((0))

        if path:
            self.player.set_state(gst.STATE_NULL)
            #self.playlbl.set_label("Play")
            self.set_play_pause()
            #print "play:",path
            self.player.set_property('uri', path ) #"file://" + filepath)
            self.player.set_state(gst.STATE_PLAYING)
            self._start_queries()
            if self.cur_index > -1:
                self.treeview.set_cursor((self.cur_index,))
            self.paused = False
            self.metadata.clear()
            #vol = self.player.get_property("volume")
            #print "Volume:",vol
            

    def add_columns(self):
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Track', renderer, text=COLUMN_NUMBER)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Length', renderer, text=COLUMN_STRING)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)
        
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Title', renderer, text=2)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)
        
    
    def row_activated(self,w,path,view_column):#user dbl clicked treeview
        fpath = self.get_file_path(path)
        self.start_stop(fpath)
        
    def get_file_selected(self):
        model,iter = self.treeview.get_selection().get_selected()
        if model and iter:
            self.cur_index = model.get(iter,0)[0] - 1
            return model.get(iter,3)[0]
        return None
        
    def get_file_path(self,path):
        model = self.treeview.get_model()
        if model:
            print path
            iter = model.get_iter(path)
            self.cur_index = model.get(iter,0)[0] - 1
            return model.get(iter,3)[0]
        return None
    
    def convert_ns(self, time_int):
        time_int = time_int / 1000000000
        sec = time_int % 60
        min = time_int % 3600 / 60
        hour = time_int / 3600
        if hour == 0:
            return "%02d:%02d"%(min,sec)
        return "%d:%02d:%02d"%(hour,min,sec)
        
        '''srsly?
            def convert_ns(self, time_int):
                time_int = time_int / 1000000000
                time_str = ""
                if time_int >= 3600:
                    _hours = time_int / 3600
                    time_int = time_int - (_hours * 3600)
                    time_str = str(_hours) + ":"
                if time_int >= 600:
                    _mins = time_int / 60
                    time_int = time_int - (_mins * 60)
                    time_str = time_str + str(_mins) + ":"
                elif time_int >= 60:
                    _mins = time_int / 60
                    time_int = time_int - (_mins * 60)
                    time_str = time_str + "%02d"%_mins + ":"
                else:
                    time_str = time_str + "00:"
                #if time_int > 9:
                #    time_str = time_str + str(time_int)
                #else:
                time_str = time_str + "%02d"%time_int
                    
                return time_str
        '''

    def add_data(self,data, add = False):
        ls = None
        
        if add:
            ls = self.treeview.get_model()
            
        if not add or not ls:
            ls = gtk.ListStore(gobject.TYPE_UINT, gobject.TYPE_STRING,
                            gobject.TYPE_STRING,gobject.TYPE_STRING)
            
        for item in data:
            iter = ls.append()
            ls.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
        
        self.treeview.set_model(ls)
    
    def set_tree_file_length(self,time):
        try:
            ls = self.treeview.get_model()
            iter = ls.get_iter( (self.cur_index ,) )
            ls.set_value(iter,1,time)
        except:
            print "Failed to set treeview time"
            
    def change_visualisation(self,widget,vis_name): 
        #Some not so mature vis plugins hang the pipeline 
        #when changed while in Playing state
        vis = None
        if vis_name!=None:
            #try: 
            vis = gst.element_factory_make(vis_name)
            #except Exception,e: 
            #    vis = None
            
            if vis:
                self.player.set_property('vis-plugin',vis)
        else:
            self.player.set_property('vis-plugin',None)
        
    def destroy(klass, widget=None):
        klass.on_stop(None)
        gtk.main_quit()
        
    def on_open(self,w):
        f = gtk.FileChooserDialog("Open M3U", self.window, gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons = ( gtk.STOCK_CANCEL,gtk.RESPONSE_CLOSE,
                        gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        filter = gtk.FileFilter()
        filter.set_name("M3U Playlist")
        filter.add_mime_type("audio/x-mpegurl")
        filter.add_pattern("*.m3u")
        f.add_filter(filter)

        filter = gtk.FileFilter()
        filter.set_name("Audio/Video files")
        filter.add_mime_type("audio/*")
        filter.add_mime_type("video/*")
        
        filter.add_pattern("*.ogg")
        filter.add_pattern("*.mp3")
        f.add_filter(filter)
        
        filter = gtk.FileFilter()
        filter.set_name("Url")
        filter.add_pattern("*.url")
        f.add_filter(filter)
        
        f.connect("response",self.dialog_response)
        f.show_all()
    
    def dialog_response(self,w,s):
        if s == gtk.RESPONSE_OK:
            if w.get_filter().get_name() == "M3U Playlist":
                self.open_m3u(w.get_filename())
            if w.get_filter().get_name() == "Audio/Video files":
                self.open_file(w.get_filename())
            if w.get_filter().get_name() == "Url":
                print w.get_filename()
                import re
                r = re.compile('.*?(http:|ftp:)(.*)',re.IGNORECASE)
                m = r.match(w.get_filename())
                
                if m:
                    self.open_url(m.group(1)+"/"+m.group(2))
        w.destroy()
    
    def open_m3u(self,file):
        f = open(file,"r")
        data = []
        idx = 0
        max_bs_lines = 0
        while True:
            line = f.readline()
            max_bs_lines += 1
            if line.startswith("#EXTM3U"):
                break
            if max_bs_lines >= 100: # Too much crap at the beginning, ignore
                return
                
        while True:
            ext = f.readline()
            fn = f.readline()
            if not ext:
                break
            idx += 1
            ext_split = ext[8:].strip('\r\n').split(",",1)
            t0 = int(ext_split[0])
            time = ("%02d:%02d")%(t0/60,t0%60)
            
            data.append([idx, time, ext_split[1], fn.strip('\r\n') ])
        self.count = idx
        self.add_data(data)
        
    def open_file(self,file):
        self.count += 1
        data = [[self.count, "00:00", file.rsplit("/",1)[1] ,"file://"+file ]] #os.path.separator or what it is :P
        self.add_data(data, True)

    def open_url(self,url):
        print "Adding url:", url
        self.count += 1
        data = [[self.count, "00:00", url.rsplit("/",1)[1] ,url ]] #os.path.separator or what it is :P
        self.add_data(data, True)
                
    def _start_queries(self):
        def do_query():
            try:
                pos_int = self.get_position_ns()
                self.statuses['pos'] = self.convert_ns(pos_int)
                self.set_status()
                if not self.seeking and self.can_seek:
                    self.update_seeker = True
                    self.seeker.set_value(pos_int / 1000000000)
                    self.update_seeker = False
            except:
                # print 'query failed'
                pass
            return True
        if self._query_id == -1:
            self._query_id = gobject.timeout_add(100, # 10 Hz
                                                 do_query)

    def _stop_queries(self):
        if self._query_id != -1:
            gobject.source_remove(self._query_id)
            self._query_id = -1

class VideoWidget(gtk.EventBox):
    def __init__(self,gst_player):
        self.gst_player = gst_player
        gtk.EventBox.__init__(self)
        self.connect("button_press_event", self.on_click )
        self.imagesink = None
        self.is_fullscreen = False
        self.varea = VideoArea()
        #self.varea.myinit()
        self.add(self.varea)
        self.visu_plugins = None

        self.loaded = False
        #import thread
        #self.lock = thread.allocate_lock()

    def set_sink(self, sink):
        self.imagesink = sink
        #gobject.idle_add(self.set_window_id)
        self.set_window_id()
        self.child.set_sink(sink)
        if self.is_fullscreen:
            self.fwindow.child.child.set_sink(sink)

        """
        workaround to launch the visualisation on startup
        And prevent the "Xerror GC bad" problem when visualisation start and widget not completey realize
        """
        if not self.loaded:
            self.child.do_expose_event(None)
            self.loaded = True

    def set_window_id(self):
        if self.is_fullscreen:
            self.imagesink.set_xwindow_id(self.fwindow.child.child.window.xid)
        else:
            self.imagesink.set_xwindow_id(self.child.window.xid)

    def on_click(self,btn,event):
        if self.gst_player.paused:
            return
        
        if event.button == 3:
            menu = gtk.Menu()
            i = 0
            for name,detail in self.get_list_plugins():
                label = gtk.Label()
                label.set_markup(detail)
                label.set_alignment(0.0, 0.0)
                item = gtk.MenuItem()
                item.add(label)
                item.connect("activate",self.gst_player.change_visualisation,name)
                menu.attach(item, 0, 1, i, i+1)
                i +=1

            menu.show_all()
            menu.popup(None, None, None, event.button, event.time)

        if event.button == 1:
            self.toggle_fullscreen()


    def toggle_fullscreen(self):
        
        #self.lock.acquire()
        if self.is_fullscreen:
            self.is_fullscreen = False
            #self.imagesink.set_property("force-aspect-ratio", True)
            try:
                self.set_window_id()
            except:
                pass
            self.fwindow.destroy()
        else:
            #self.imagesink.set_property("force-aspect-ratio", False) #Acshully check if vis first, then..
            self.fwindow = gtk.Window(gtk.WINDOW_POPUP)
            self.fwindow.set_decorated(False)
            self.fwindow.set_position(gtk.WIN_POS_CENTER_ALWAYS)
            monitor = gtk.gdk.Screen.get_monitor_geometry(gtk.gdk.screen_get_default(), 0)
            e = gtk.EventBox()
            self.varea = VideoArea(self.imagesink)
            e.add(self.varea)
            e.connect("button_press_event", self.on_click )
            self.fwindow.add(e)
            self.fwindow.resize(monitor.width,monitor.height)
            #self.fwindow.resize(300,300)
            e.show_all()
            self.is_fullscreen = True
            self.fwindow.show_all()
            self.fwindow.set_keep_above(True)
            gtk.gdk.flush()
            self.set_window_id()
            
        #self.lock.release()

    def get_list_plugins(self):
        if self.visu_plugins==None:
            self.visu_plugins = []
            #plugs = gst.registry_get_default().get_plugin_list()
            #for p in plugs:
            #    for elem_factory in gst.registry_get_default().get_feature_list_by_plugin(p.get_name()):
            #        if isinstance(elem_factory,gst.ElementFactory):
            #            if elem_factory.get_klass().rfind("Visualization")!=-1:
            #                self.visu_plugins.append((p.get_name(),elem_factory.get_longname()))
            
            #Because ^^ for some reason didn't work always :S
            r = gst.registry_get_default()
            for x in r.get_feature_list(gst.ElementFactory):
                if (gst.ElementFactory.get_klass(x) == "Visualization"):
                    self.visu_plugins.append((x.get_name(),x.get_longname()))
        return self.visu_plugins

class VideoArea(gtk.DrawingArea):
    def __init__(self,imagesink=None):
        gtk.DrawingArea.__init__(self)
        self.unset_flags(gtk.DOUBLE_BUFFERED)
        self.imagesink = imagesink
        self.connect("expose_event", self.expose)

    def set_sink(self,imagesink):
        self.imagesink = imagesink

    def expose(self, widget, event):
        x,y,w,h = self.get_allocation()
        self.context = widget.window.cairo_create()
        
        # set a clip region for the expose event
        self.context.rectangle(event.area.x, event.area.y,
                               event.area.width, event.area.height)
        self.context.clip()
        self.draw(self.context)
        self.do_expose_event(event)
        return False
    
    def draw(self, context):
        x,y,w,h = self.get_allocation()
        context.set_source_rgb(0,0,0)
        context.rectangle(0,0,w,h)
        context.fill()
        
    def do_expose_event(self, event):
        #self.expose(self,event)
        if self.imagesink:
            #self.imagesink.expose() # keeps showing garbage when 'unfullscreening'
                                     # crashes sometimes, x out of sync
            return False
        else:
            return True


#from __future__ import with_statement
import sys,linecache
def traceit(frame, event, arg):
    if event == "line":
        lineno = frame.f_lineno
        filename = frame.f_globals["__file__"]
        if filename == "<stdin>":
            filename = "glade.py"
        if (filename.endswith(".pyc") or
            filename.endswith(".pyo")):
            filename = filename[:-1]
        name = frame.f_globals["__name__"]
        line = linecache.getline(filename, lineno)
        print "%s:%s: %s" % (name, lineno, line.rstrip())

    return traceit
    
if __name__ == "__main__":
    #sys.settrace(traceit)
    g = Gui()
    gtk.gdk.threads_init()
    gtk.main()
